structure Board = struct
    type board = int vector vector;
    fun fromString str =
        let
            fun compose a b = (fn input => b (a input));

            val cleanup_rows =
                compose
                    (compose
                         (map (StringExtra.replace "  " " "))
                         (map (StringExtra.trim_left (fn c => c = #" "))))
                    (map (String.tokens (fn c => c = #" ")));

            val string_list_to_int_list =
                map (fn num_as_str =>
                        case Int.fromString num_as_str of
                            SOME number => number
                          | NONE =>
                            raise Fail "encountered a non-int string");

            val row_strs = (String.tokens (fn c => c = #"\n")) str;
            val rows_as_str_vals = cleanup_rows row_strs;
            val rows = map string_list_to_int_list rows_as_str_vals;
            val rows_as_vecs = map Vector.fromList rows;
        in
            (Vector.fromList rows_as_vecs): board
        end;

    fun getHeight board = Vector.length board;
    fun getWidth board = Vector.length (Vector.sub (board, 0));

    fun getCol (col_ind, board) =
        let
            val height = getHeight board;
            fun iter row_ind =
                if row_ind < height
                then Vector.sub (Vector.sub (board, row_ind), col_ind) :: iter (row_ind + 1)
                else [];
        in
            Vector.fromList (iter 0)
        end;

    fun getRow (row_ind, board) = Vector.sub (board, row_ind);

    fun getRows board =
        let
            val height = getHeight board;
            fun iter_rows ind_row =
                if ind_row < height
                then getRow (ind_row, board) :: iter_rows (ind_row + 1)
                else nil;
        in
            iter_rows 0
        end;

    fun getCols board =
        let
            val width = getWidth board;
            fun iter_cols ind_col =
                if ind_col < width
                then getCol (ind_col, board) :: iter_cols (ind_col + 1)
                else nil;
        in
            iter_cols 0
        end;

    fun getAllRowsAndColumns board = (getRows board) @ (getCols board);

    fun sum board = map VectorExtra.sum (getRows board);

    fun contains board num =
        List.foldl (fn (elem, acc) => acc orelse elem)
                   false
                   (map (Vector.exists (fn elem => elem = num))
                        (getRows board));

    fun unselected_numbers board num_seq =
        let
            val rows = getRows board;
            val in_num_seq = (fn vec_elem => (List.exists (fn e => e = vec_elem) num_seq));
            val pred_not_in_seq = (fn row_elem => not (in_num_seq row_elem));
            val keep_only_not_in_seq_elems = (fn row => VectorExtra.filter pred_not_in_seq row);
            val not_mentioned_nums =
                ListExtra.flatten (map keep_only_not_in_seq_elems rows);
        in
            not_mentioned_nums
        end;

    fun sum_unselected (board, num_seq) =
        foldl op+ 0 (unselected_numbers board num_seq);

    fun is_line_completed (line, num_seq) =
        VectorExtra.all_in_list line num_seq;

    fun has_completed_line (board, num_seq) =
        List.exists (fn line => is_line_completed (line, num_seq))
                    (getAllRowsAndColumns board);
end;
